﻿// John Lake
// CSCE 441

#include <stdio.h>
#include <list>
#include <iostream>
#include <fstream>
#include "RayTracer.h"
#include "./constants.h"
#include <gl/glut.h>
#include <gl/gl.h>
#include <cmath>



float framebuffer[ImageH][ImageW][3];
std::vector<Object*> scene;	// contains the objects in the scene
Color ambientLight;
LightingPackage lp;
int counter;

Point eyeLocation = Point(ImageW/2, ImageH/2, -ImageD);


Color findPixelColor(Ray ray)
{
	Color color = Color(0,0,0);
	// 1. iterate through the scene, finding intersections
	std::vector<Object*>::iterator oIter;

	int indexofIntersected = -1;
	float parameterofIntersected = -1;

	for(oIter = scene.begin(); oIter != scene.end(); oIter++)
	{
		Ray invDefRay = Ray((*oIter)->deformationInverse*ray.p(), (*oIter)->deformationInverse*ray.v());

		float parameter = (*oIter)->intersection(invDefRay);
		if(parameter >= 0)	// intersection!
		{
			if((parameter < parameterofIntersected) || (parameterofIntersected == -1))	// closer (or new) object
			{
				parameterofIntersected = parameter;
				indexofIntersected = (*oIter)->index;
			}
		}
	}

	//if(indexofIntersected != -1)
	if(indexofIntersected >= 0)
	{	// reflection intersections!
		for(oIter = scene.begin(); oIter != scene.end(); oIter++)
		{
			if((*oIter)->index == indexofIntersected)
				break;
		}

		//  c. recurse (increasing depth)
		color = (*oIter)->lightingFunction(ray, parameterofIntersected, 0, ambientLight, lp);
	}


	// 2. return the color at the intersection
	return color;
}

// Draws the scene
void drawit(void)
{
   glDrawPixels(ImageW,ImageH,GL_RGB,GL_FLOAT,framebuffer);
   glFlush();
}


// Clears framebuffer to black
void clearFramebuffer()
{
	int i,j;

	for(i=0;i<ImageH;i++) {
		for (j=0;j<ImageW;j++) {
			framebuffer[i][j][0] = 0.0;	// R
			framebuffer[i][j][1] = 0.0;	// G
			framebuffer[i][j][2] = 0.0;	// B
		}
	}
}


// Sets pixel x,y to the color RGB
void setFramebuffer(int x, int y, float R, float G, float B)
{
	if (R<=1.0)
		if (R>=0.0)
			framebuffer[y][x][0]=R;
		else
			framebuffer[y][x][0]=0.0;
	else
		framebuffer[y][x][0]=1.0;
	if (G<=1.0)
		if (G>=0.0)
			framebuffer[y][x][1]=G;
		else
			framebuffer[y][x][1]=0.0;
	else
		framebuffer[y][x][1]=1.0;
	if (B<=1.0)
		if (B>=0.0)
			framebuffer[y][x][2]=B;
		else
			framebuffer[y][x][2]=0.0;
	else
		framebuffer[y][x][2]=1.0;
}



void display(void)
{
	clearFramebuffer();

	if(scene.empty())
	{
		drawit();
		return;
	}

	// else

	// draw scene
	for(int i = 0; i < ImageW; i++)
	{
		for(int j = 0; j < ImageH; j++)
		{

			if(counter >= 5)
			{
				drawit();
				counter = 0;
			}

			Ray ray = Ray(eyeLocation, Point(i,j,0) - eyeLocation);
			Color c = findPixelColor(ray);
			setFramebuffer(i,j,c.r,c.g,c.b);
			counter++;
		}
	}

	drawit();
}


void keyboard(unsigned char key, int x, int y)
{
	std::vector<Object*>::iterator oIter;

	scene.clear();
	lp.lights.clear();

	switch(key) {
	case '1':
		// Scene 1
		{	// code block used in an attempt to avoid an "initialization skipped by 'case' label" error

			Object* op1 = new Plane(Normal(0,1,0), Point(ImageW, 0, ImageD+1), 1);

			op1->setKa(Color(0.1,0.1,0.1));
			op1->setKd(Color(0.2,0.2,0.2));
			op1->setKs(Color(0.3,0.3,0.3));
			scene.push_back(op1);

			Object* op2 = new Sphere(Point(5*ImageW/12, ImageH/4, 2/3*ImageD), ImageW/12, 2);

			op2->setKa(Color(0.1,0,0));
			op2->setKd(Color(1,0.1,0.1));
			op2->setKs(Color(0.7,0.2,0.2));
			op2->setReflCoeff(0);
			scene.push_back(op2);


			Object* op3 = new Sphere(Point(3*ImageW/6, ImageH/2, ImageD), ImageW/2, 3);
			op3->setKa(Color(0,0,0.1));
			op3->setKd(Color(0.1,0.1,1));
			op3->setKs(Color(0.2,0.2,0.7));
			op2->deformationInverse.data[1][1] = 0.5;	// not the safest way
			op3->setReflCoeff(0);
			scene.push_back(op3);


			lp.lights.push_back(Light(Color(1,1,1), Point(1.5*ImageW, ImageH/2, ImageD/2)));
			lp.lights.push_back(Light(Color(1,1,1), Point(ImageW/6, ImageH/4, ImageD/2)));
			lp.lights.push_back(Light(Color(1,1,1), Point(ImageW/2, ImageH/2, -ImageD/2)));
			ambientLight = Color(0.5, 0.5, 0.5);


		}
		break;

	case '2':
		// Scene 2
		{
			Object* op1 = new Sphere(Point(ImageW/8, ImageH/2, ImageD), ImageW/3, 1);
			Object* op2 = new Cylinder(Point(3.5*ImageW/4, ImageH/2, 3*ImageD/4), Normal(0,1,0), ImageD/4, 2);
			Object* op3 = new Sphere(Point(ImageW/2, ImageH/2, ImageD/4), ImageH/10, 3);
			Object* op4 = new Plane(Normal(0,0,-1), Point(ImageW, ImageH, ImageD+1), 4);


			op1->setKa(Color(0.1,0,0));
			op1->setKd(Color(0.7,0.1,0.1));
			op1->setKs(Color(0.5,0.5,0.5));

			scene.push_back(op1);


			op2->setKa(Color(0,0.1,0));
			op2->setKd(Color(0.1,0.8,0.1));
			op2->setKs(Color(0.5,0.5,0.5));

			scene.push_back(op2);


			op3->setKa(Color(0,0,0.1));
			op3->setKd(Color(0.1,0.1,0.8));
			op3->setKs(Color(0.5,0.5,0.5));

			scene.push_back(op3);


			op4->setKa(Color(0.5,0.1,0.1));
			op4->setKd(Color(0.8,0.1,0.1));
			op4->setKs(Color(0.5,0.5,0.5));

			scene.push_back(op4);


			lp.lights.push_back(Light(Color(1,0,0), Point(0, 0, -10)));
			lp.lights.push_back(Light(Color(0,1,0), Point(ImageW, 0, -30)));
			lp.lights.push_back(Light(Color(0,0,1), Point(7*ImageW/8, ImageH/2, ImageD/4)));
			lp.lights.push_back(Light(Color(1,1,1), Point(ImageW/2, ImageH/2, -ImageD/4)));

			ambientLight = Color(0.5, 0.5, 0.5);
		}
		break;

	case '3':
		// Scene 3
		{
			Object* op1 = new Plane(Normal(0,1,0), Point(ImageW, ImageH/6, ImageD+1), 1);

			op1->setKa(Color(0.1,0.1,0.1));
			op1->setKd(Color(0.2,0.2,0.2));
			op1->setKs(Color(0.3,0.3,0.3));
			op1->setReflCoeff(0.5);
			scene.push_back(op1);

			Object* op2 = new Sphere(Point(ImageW/6, ImageH/2, ImageD/2), ImageW/3, 2);

			op2->setKa(Color(0.1,0,0));
			op2->setKd(Color(0.7,0.1,0.1));
			op2->setKs(Color(0.5,0.2,0.2));
			scene.push_back(op2);

			op2->setReflCoeff(0.5);

			Object* op3 = new Sphere(Point(5*ImageW/6, ImageH/2, ImageD/2), ImageW/3, 3);
			op3->setKa(Color(0,0,0.1));
			op3->setKd(Color(0.1,0.1,0.7));
			op3->setKs(Color(0.2,0.2,0.6));
			scene.push_back(op3);

			op3->setReflCoeff(0.5);

			lp.lights.push_back(Light(Color(1,1,1),Point(ImageW/2,ImageH/2,ImageD/4)));
			lp.lights.push_back(Light(Color(1,1,1), Point(ImageW, ImageH, ImageD/4)));
			lp.lights.push_back(Light(Color(1,1,1), Point(0, ImageH, ImageD/4)));
			ambientLight = Color(0.5, 0.5, 0.5);

		}			
		break;

	case '5':
		// Scene 5
		{

			double KdC = 0.7;
			double KsC = 0.5;
			
			Object* op1 = new Plane(Normal(0, 1, 0), Point(ImageW, ImageH / 6, ImageD + 1), 1);

			op1->setKa(Color(0.1, 0.1, 0.1));
			op1->setKd(Color(0.2, 0.2, 0.2));
			op1->setKs(Color(0.3, 0.3, 0.3));
			op1->setReflCoeff(0.5);
			scene.push_back(op1);

			Object* op2 = new Sphere(Point(1 * ImageW / 6, ImageH / 2, ImageD / 2), ImageW / 3, 2);

			op2->setKa(Color(0.1, 0, 0));
			op2->setKd(Color(226.0 / 255 * KdC, 41.0 / 255 * KdC, 130.0 / 255 * KdC));
			op2->setKs(Color(226.0 / 255 * KsC, 41.0 / 255 * KsC, 130.0 / 255 * KsC));
			scene.push_back(op2);

			op2->setReflCoeff(0.5);

			Object* op3 = new Sphere(Point(5 * ImageW / 6, ImageH / 2, ImageD / 2), ImageW / 3, 3);
			op3->setKa(Color(0, 0, 0.1));
			op3->setKd(Color(0.0 / 255 * KdC, 78.0 / 255 * KdC, 183.0 / 255 * KdC));
			op3->setKs(Color(0.0 / 255 * KsC, 78.0 / 255 * KsC, 183.0 / 255 * KsC));
			scene.push_back(op3);

			op3->setReflCoeff(0.5);

			Object* op4 = new Sphere(Point(3 * ImageW / 6, ImageH / 3, ImageD / 6), ImageW / 6, 4);
			op4->setKa(Color(0, 0, 0.1));
			op4->setKd(Color(135.0 / 255 * KdC, 102.0 / 255 * KdC, 166.0 / 255 * KdC));
			op4->setKs(Color(135.0 / 255 * KsC, 102.0 / 255 * KsC, 166.0 / 255 * KsC));
			scene.push_back(op4);

			op4->setReflCoeff(0.5);


			lp.lights.push_back(Light(Color(1,1,1),Point(ImageW/2,ImageH/2,ImageD/4)));
			lp.lights.push_back(Light(Color(1,1,1), Point(ImageW, ImageH, 0*ImageD/8)));
			lp.lights.push_back(Light(Color(1,1,1), Point(0, ImageH, 0*ImageD/8)));
			lp.lights.push_back(Light(Color(0.2,0.2,0.2), Point(ImageW/2, ImageH/2, -ImageD/9)));
			ambientLight = Color(0.5, 0.5, 0.5);

		}
		break;
		
	default:
		// Scene 4 - Solar System
		{
			Object* Mercury = new Sphere(Point(ImageW, 0.1*ImageH, 0.2*ImageD), 0.08*ImageW, 1);
			Mercury->setKa(Color(0.05,0.05,0.05));
			Mercury->setKd(Color(0.3,0.3,0.3));
			Mercury->setKs(Color(0,0,0));	// don't want specular reflections
			Mercury->setReflCoeff(0);
			scene.push_back(Mercury);

			Object* Venus = new Sphere(Point(0.83*ImageW, 0.15*ImageH, 0.45*ImageD), 0.13*ImageW, 2);
			Venus->setKa(Color(0.05,0.05,0.05));
			Venus->setKd(Color(0.6,0.4,0.1));
			Venus->setKs(Color(0,0,0));	// don't want specular reflections
			Venus->setReflCoeff(0);
			scene.push_back(Venus);

			Object* Gaia = new Earth(Point(0.5*ImageW, 0.25*ImageH, 0.65*ImageD), 0.17*ImageW, 3);
			// Ka, Ks, Kd defined as part of this class
			Gaia->setReflCoeff(0);
			scene.push_back(Gaia);

			Object* Mars = new Sphere(Point(0.25*ImageW, 0.3*ImageH, 0.8*ImageD), 0.14*ImageW, 4);
			Mars->setKa(Color(0.05,0.05,0.05));
			Mars->setKd(Color(0.4,0.1,0));
			Mars->setKs(Color(0,0,0));
			Mars->setReflCoeff(0);
			scene.push_back(Mars);

			Object* Zeus = new Jupiter(Point(0*ImageW, 0.55*ImageH, ImageD), 0.3*ImageW, 5);
			// Ka, Ks, Kd defined as part of this class
			Zeus->setReflCoeff(0);
			scene.push_back(Zeus);

			Object* Saturn = new Sphere(Point(0.4*ImageW, 0.8*ImageH, 1.2*ImageD), 0.25*ImageW, 6);
			Saturn->setKa(Color(0.05,0.05,0.05));
			Saturn->setKd(Color(0.9,0.8,0));
			Saturn->setKs(Color(0,0,0));
			Saturn->setReflCoeff(0);
			scene.push_back(Saturn);

			Object* Uranus = new Sphere(Point(0.9*ImageW, 1*ImageH, 1.8*ImageD), 0.23*ImageW, 7);
			Uranus->setKa(Color(0.05,0.05,0.05));
			Uranus->setKd(Color(0,0.8,0.8));
			Uranus->setKs(Color(0,0,0));
			Uranus->setReflCoeff(0);
			scene.push_back(Uranus);

			Object* Poseidon = new Neptune(Point(1.2*ImageW, 1.25*ImageH, 2*ImageD), 0.22*ImageW, 8);
			Poseidon->setReflCoeff(0);
			scene.push_back(Poseidon);

			Object* Pluto = new Sphere(Point(1.6*ImageW, 1.5*ImageH, 2.2*ImageD), 0.11*ImageW, 9);
			Pluto->setKa(Color(0.05,0.05,0.05));
			Pluto->setKd(Color(0.4,0.4,0.4));
			Pluto->setKs(Color(0,0,0));
			Pluto->setReflCoeff(0);
			scene.push_back(Pluto);

			
			Object* Moon = new Sphere(Point(0.64*ImageW, 0.4*ImageH, 0.75*ImageD), 0.12*ImageW, 10);
			Moon->setKa(Color(0.05,0.05,0.05));
			Moon->setKd(Color(0.6,0.6,0.6));
			Moon->setKs(Color(0,0,0));	// don't want specular reflections
			Moon->setReflCoeff(0);
			scene.push_back(Moon);


			Object* Charon = new Sphere(Point(1.85*ImageW, 1.7*ImageH, 2.4*ImageD), 0.07*ImageW, 11);
			Charon->setKa(Color(0.05,0.05,0.05));
			Charon->setKd(Color(0.6,0.6,0.6));
			Charon->setKs(Color(0,0,0));	// don't want specular reflections
			Charon->setReflCoeff(0);
			scene.push_back(Charon);


			Object* Io = new Sphere(Point(-0.15*ImageW, 0.6*ImageH, 0.5*ImageD), 0.03*ImageW, 12);
			Io->setKa(Color(0.05,0.05,0.05));
			Io->setKd(Color(0.4,0.4,0.1));
			Io->setKs(Color(0,0,0));	// don't want specular reflections
			Io->setReflCoeff(0);
			scene.push_back(Io); 


			Object* Europa = new Sphere(Point(-0.06*ImageW, 0.5*ImageH, 0.5*ImageD), 0.03*ImageW, 13);
			Europa->setKa(Color(0.05,0.05,0.05));
			Europa->setKd(Color(0.1,0.2,0.6));
			Europa->setKs(Color(0,0,0));	// don't want specular reflections
			Europa->setReflCoeff(0);
			scene.push_back(Europa); 


			Object* Ganymede = new Sphere(Point(-0.13*ImageW, 0.4*ImageH, 0.5*ImageD), 0.03*ImageW, 14);
			Ganymede->setKa(Color(0.05,0.05,0.05));
			Ganymede->setKd(Color(0.35,0.35,0.35));
			Ganymede->setKs(Color(0,0,0));	// don't want specular reflections
			Ganymede->setReflCoeff(0);
			scene.push_back(Ganymede); 


			Object* Callisto = new Sphere(Point(-0.04*ImageW, 0.7*ImageH, 0.5*ImageD), 0.03*ImageW, 15);
			Callisto->setKa(Color(0.05,0.05,0.05));
			Callisto->setKd(Color(0.4,0.3,0.35));
			Callisto->setKs(Color(0,0,0));	// don't want specular reflections
			Callisto->setReflCoeff(0);
			scene.push_back(Callisto); 


			Object* Phobos = new Sphere(Point(0.21*ImageW, 0.33*ImageH, 0.6*ImageD), 0.01*ImageW, 16);
			Phobos->setKa(Color(0.05,0.05,0.05));
			Phobos->setKd(Color(0.45,0.45,0.45));
			Phobos->setKs(Color(0,0,0));	// don't want specular reflections
			Phobos->setReflCoeff(0);
			scene.push_back(Phobos); 


			Object* Deimos = new Sphere(Point(0.22*ImageW, 0.24*ImageH, 0.6*ImageD), 0.01*ImageW, 17);
			Deimos->setKa(Color(0.05,0.05,0.05));
			Deimos->setKd(Color(0.45,0.45,0.45));
			Deimos->setKs(Color(0,0,0));	// don't want specular reflections
			Deimos->setReflCoeff(0);
			scene.push_back(Deimos); 


			Object* Triton = new Sphere(Point(1.3*ImageW, 1.1*ImageH, 1.6*ImageD), 0.035*ImageW, 18);
			Triton->setKa(Color(0.05,0.05,0.05));
			Triton->setKd(Color(0.6,0.6,0.6));
			Triton->setKs(Color(0,0,0));	// don't want specular reflections
			Triton->setReflCoeff(0);
			scene.push_back(Triton); 


			Object* Titan = new Sphere(Point(0.6*ImageW, 0.64*ImageH, 0.9*ImageD), 0.03*ImageW, 19);
			Titan->setKa(Color(0.05,0.05,0.05));
			Titan->setKd(Color(0.6,0.6,0.2));
			Titan->setKs(Color(0,0,0));	// don't want specular reflections
			Titan->setReflCoeff(0);
			scene.push_back(Titan); 


			Object* SaturnRings1 = new Ring(Point(0.4*ImageW, 0.8*ImageH, 1.2*ImageD), 0.27*ImageW, 0.32*ImageW, Normal(-1,1.3,-1), 20);
			SaturnRings1->setKa(Color(0.05,0.05,0.05));
			SaturnRings1->setKd(Color(1,0.6,0));
			SaturnRings1->setKs(Color(0,0,0));
			SaturnRings1->setReflCoeff(0);
			scene.push_back(SaturnRings1);


			Object* SaturnRings2 = new Ring(Point(0.4*ImageW, 0.8*ImageH, 1.2*ImageD), 0.34*ImageW, 0.4*ImageW, Normal(-1,1.3,-1), 21);
			SaturnRings2->setKa(Color(0.05,0.05,0.05));
			SaturnRings2->setKd(Color(1,0.6,0));
			SaturnRings2->setKs(Color(0,0,0));
			SaturnRings2->setReflCoeff(0);
			scene.push_back(SaturnRings2);


			Object* UranusRings = new Ring(Point(0.9*ImageW, 1*ImageH, 1.8*ImageD), 0.26*ImageW, 0.29*ImageW, Normal(0.01,0,-1), 22);
			UranusRings->setKa(Color(0.05,0.05,0.05));
			UranusRings->setKd(Color(0.25,0.25,0.25));
			UranusRings->setKs(Color(0.1,0.1,0.1));
			UranusRings->setReflCoeff(0);
			scene.push_back(UranusRings);



			Light sun = Light(Color(1,1,1), Point(ImageW/2, ImageH/2, -ImageD));
			lp.lights.push_back(sun);
		}			
		break;
	}

	lp.objects = scene;
	glutPostRedisplay();
}

void init(void)
{
	gluOrtho2D ( 0, ImageW - 1, ImageH - 1, 0 );
	clearFramebuffer();
}

int main(int argc, char** argv)
{
	counter = 0;

	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);
	glutInitWindowSize(ImageW,ImageH);
	glutInitWindowPosition(100,100);
	glutCreateWindow("John Lake - Homework 5");

	init();	

	glutDisplayFunc(display);
	glutKeyboardFunc( keyboard );
	glutMainLoop();
	return 0;
}
